#pragma once

#include <vex/core/config.h>
#include <vex/core/make_raw_handle.h>
#include <vex/functional/any_delegate.h>
#include <vex/functional/contract/multicast_delegate.h>
#include <vex/functional/implementation/bs2_connection_impl.h>
#include <vex/functional/implementation/default_value.h>
#include <boost/signals2.hpp>
#include <boost/function_types/result_type.hpp>
#include <functional>

namespace vex { namespace functional { namespace implementation {

    namespace bs2 = boost::signals2;
    namespace bft = boost::function_types;

    template<
        class TSignature, 
        class TCombiner = bs2::optional_last_value<typename bft::result_type<TSignature>::type>
    >
    struct bs2_signal {
        typedef bs2::signal<TSignature, 
            TCombiner, 
            int, 
            std::less<int>,
            any_delegate<TSignature>,
            any_delegate<typename bft::result_type<TSignature>::type (bs2::connection const &, ...)>
        > type;
    };

    template<class TSignature>
    struct bs2_signal_traits {
        typedef typename bs2_signal<TSignature>::type signal_type;
        typedef bs2_connection_impl connection_impl_type;
        typedef contract::delegate<TSignature> delegate_type;
        typedef contract::multicast_delegate<TSignature> multicast_delegate_type;
        typedef typename multicast_delegate_type::invocation_list_type invocation_list_type;
        typedef typename bft::result_type<TSignature>::type result_type;
        typedef default_value<TSignature> default_value_type;

        static contract::connection* connect(
            signal_type& p_signal, 
            delegate_type* p_delegate,
            connection_required::domain p_conn_required)
        {
            auto t_connection = p_signal.connect(any_delegate<TSignature>(p_delegate));
            return p_conn_required ?
                core::make_raw_handle<connection_impl_type>(std::move(t_connection)) : nullptr;
        }

#ifdef VEX_HAS_CPP_11_FEATURE_SET_3
            template<class ...TArgs>
            static void invoke_all(
                default_value<TSignature, void> const &, signal_type const p_signal, 
                TArgs...&& p_args) const
            {
                return p_signal(std::forward<TArgs>(p_args)...);
            }

            template<class TRet, class ...TArgs>
            static TRet invoke_all(
                default_value<TSignature, TRet> const & p_default, signal_type const p_signal, 
                TArgs...&& p_args) const
            {
                auto const && t_optional_res = p_signal(std::forward<TArgs>(p_args)...);
                return t_optional_res.get_value_or(std::forward<TRet>(p_default()));
            }
#else
        static void invoke_all(
            default_value<TSignature, void> const &, signal_type const& p_signal)
        {
            return p_signal();
        }

        template<class TRet>
        static TRet invoke_all(
            default_value<TSignature, TRet> const & p_default, signal_type const& p_signal)
        {
            auto const && t_optional_res = p_signal();
            return t_optional_res.get_value_or(std::forward<TRet>(p_default()));
        }

        template<class TArg1>
        static void invoke_all(
            default_value<TSignature, void> const &, signal_type const& p_signal, 
            TArg1&& p_arg1)
        {
            return p_signal(std::forward<TArg1>(p_arg1));
        }

        template<class TRet, class TArg1>
        static TRet invoke_all(
            default_value<TSignature, TRet> const & p_default, signal_type const& p_signal, 
            TArg1&& p_arg1)
        {
            auto const && t_optional_res = p_signal(std::forward<TArg1>(p_arg1));
            return t_optional_res.get_value_or(std::forward<TRet>(p_default()));
        }

        template<class TArg1, class TArg2>
        static void invoke_all(
            default_value<TSignature, void> const & p_default, signal_type const& p_signal, 
            TArg1&& p_arg1, 
            TArg2&& p_arg2)
        {
            return p_signal(
                std::forward<TArg1>(p_arg1),
                std::forward<TArg2>(p_arg2));
        }

        template<class TRet, class TArg1, class TArg2>
        static TRet invoke_all(
            default_value<TSignature, TRet> const & p_default, signal_type const& p_signal, 
            TArg1&& p_arg1,
            TArg2&& p_arg2)
        {
            auto const && t_optional_res = p_signal(
                std::forward<TArg1>(p_arg1),
                std::forward<TArg2>(p_arg2));
            return t_optional_res.get_value_or(std::forward<TRet>(p_default()));
        }

        template<class TArg1, class TArg2, class TArg3>
        static void invoke_all(
            default_value<TSignature, void> const & p_default, signal_type const& p_signal, 
            TArg1&& p_arg1, 
            TArg2&& p_arg2,
            TArg3&& p_arg3)
        {
            return p_signal(
                std::forward<TArg1>(p_arg1),
                std::forward<TArg2>(p_arg2),
                std::forward<TArg3>(p_arg3));
        }

        template<class TRet, class TArg1, class TArg2, class TArg3>
        static TRet invoke_all(
            default_value<TSignature, TRet> const & p_default, signal_type const& p_signal, 
            TArg1&& p_arg1,
            TArg2&& p_arg2,
            TArg3&& p_arg3)
        {
            auto const && t_optional_res = p_signal(
                std::forward<TArg1>(p_arg1),
                std::forward<TArg2>(p_arg2),
                std::forward<TArg3>(p_arg3));
            return t_optional_res.get_value_or(std::forward<TRet>(p_default()));
        }

        template<class TArg1, class TArg2, class TArg3, class TArg4>
        static void invoke_all(
            default_value<TSignature, void> const & p_default, signal_type const& p_signal, 
            TArg1&& p_arg1, 
            TArg2&& p_arg2,
            TArg3&& p_arg3,
            TArg4&& p_arg4)
        {
            return p_signal(
                std::forward<TArg1>(p_arg1),
                std::forward<TArg2>(p_arg2),
                std::forward<TArg3>(p_arg3),
                std::forward<TArg4>(p_arg4));
        }

        template<class TRet, class TArg1, class TArg2, class TArg3, class TArg4>
        static TRet invoke_all(
            default_value<TSignature, TRet> const & p_default, signal_type const& p_signal, 
            TArg1&& p_arg1,
            TArg2&& p_arg2,
            TArg3&& p_arg3,
            TArg4&& p_arg4)
        {
            auto const && t_optional_res = p_signal(
                std::forward<TArg1>(p_arg1),
                std::forward<TArg2>(p_arg2),
                std::forward<TArg3>(p_arg3),
                std::forward<TArg4>(p_arg4));
            return t_optional_res.get_value_or(std::forward<TRet>(p_default()));
        }

        template<class TArg1, class TArg2, class TArg3, class TArg4, class TArg5>
        static void invoke_all(
            default_value<TSignature, void> const &, signal_type const& p_signal, 
            TArg1&& p_arg1, 
            TArg2&& p_arg2,
            TArg3&& p_arg3,
            TArg4&& p_arg4,
            TArg5&& p_arg5)
        {
            return p_signal(
                std::forward<TArg1>(p_arg1),
                std::forward<TArg2>(p_arg2),
                std::forward<TArg3>(p_arg3),
                std::forward<TArg4>(p_arg4),
                std::forward<TArg5>(p_arg5));
        }

        template<class TRet, class TArg1, class TArg2, class TArg3, class TArg4, class TArg5>
        static TRet invoke_all(
            default_value<TSignature, TRet> const & p_default, signal_type const& p_signal, 
            TArg1&& p_arg1,
            TArg2&& p_arg2,
            TArg3&& p_arg3,
            TArg4&& p_arg4,
            TArg5&& p_arg5)
        {
            auto const && t_optional_res = p_signal(
                std::forward<TArg1>(p_arg1),
                std::forward<TArg2>(p_arg2),
                std::forward<TArg3>(p_arg3),
                std::forward<TArg4>(p_arg4),
                std::forward<TArg5>(p_arg5));
            return t_optional_res.get_value_or(std::forward<TRet>(p_default()));
        }
        // continue adding overloads...
#endif
    };
}}}